package fr.cdesire.sudoku.core.commons;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;

import fr.cdesire.sudoku.core.enums.Valeur;
import fr.cdesire.sudoku.core.exceptions.SudokuCoreException;

/**
 * Représente une grille de Sudoku constituée d'un ensemble de cases. Chacune
 * de ces cases est déterminée par une position et une ou un ensemble de valeurs.
 * @author cedric.desire@gmail.com
 */
public class Grille {

    /**
     * Association d'une case avec sa position.
     */
    private final Map<Position, Case> cases;
    
    /**
     * Format de la grille.
     */
    private final FormatGrille formatGrille;
    
    /**
     * Valeurs renseignées constituant la grille de départ.
     */
    private final Map<Position, Valeur> valeursRenseignees;

    /**
     * Constructeur d'une grille Sudoku.
     * @param nbLignesRegion Nombre de cases formant la ligne d'une région : permet le calcul du format de la grille.
     * @param nbColonnesRegion Nombre de cases formant la colonne d'une région : permet le calcul du format de la grille.
     * @throws SudokuCoreException Lève une FormatGrilleParametreException dans le cas d'une mauvaise utilisation des paramètres.
     */
    public Grille(final int nbLignesRegion, final int nbColonnesRegion) throws SudokuCoreException {

        this.formatGrille = new FormatGrille(nbLignesRegion, nbColonnesRegion);
        this.valeursRenseignees = new HashMap<Position, Valeur>();
        this.cases = initCases();
    }
    
    /**
     * Réinitialise la grille à blanc
     */
    public void clear() {
        this.valeursRenseignees.clear();
        for (Position position : formatGrille.getPositions()) {
        	cases.get(position).getValeurs().addAll(formatGrille.getValeurs());
        }
    }
    
    /**
     * Constructeur privé d'une grille : permet de cloner une grille existante.
     * @param formatGrille Format de la grille.
     * @param cases Liste des cases de la grille.
     */
    private Grille(final FormatGrille formatGrille, final Map<Position, Valeur> valeursRenseignees, final Map<Position, Case> cases) {
    	
    	this.formatGrille = formatGrille;
    	this.valeursRenseignees = valeursRenseignees;
    	this.cases = cases;	
    }

    /**
     * Retourne l'association d'une case avec sa position.
     * @return Association d'une case avec sa position.
     */
    public Map<Position, Case> getCases() {
        return cases;
    }

    /**
     * Retourne les valeurs renseignées constituant la grille de départ.
     */
    public Map<Position, Valeur> getValeursRenseignees() {
		return valeursRenseignees;
	}

	/**
     * Retourne la liste des cases vides de la grille triees par leur nombre de
     * possibilités.
     * @return Liste des cases vides de la grille.
     */
    public SortedSet<Case> getCasesVidesTriees() {

        return new TreeSet<Case>(Collections2.filter(cases.values(), new Predicate<Case>() {

            @Override
            public boolean apply(Case filtre) {
                if (!filtre.isCaseVide()) {
                    return false;
                }
                return true;
            }
        }));
    }

    /**
     * Retourne la liste des cases remplies de la grille.
     * @return Liste des cases remplies de la grille.
     */
    public Set<Case> getCasesPleines() {

        return new HashSet<Case>(Collections2.filter(cases.values(), new Predicate<Case>() {

            @Override
            public boolean apply(Case filtre) {
                if (filtre.isCaseVide()) {
                    return false;
                }
                return true;
            }
        }));
    }

    /**
     * Création de l'association entre les cases et leur position.
     * @return Association entre les cases et leur position.
     */
    private Map<Position, Case> initCases() {

        Map<Position, Case> init = new HashMap<Position, Case>();

        for (Position position : formatGrille.getPositions()) {
            init.put(position, new Case(position, formatGrille.getValeurs()));
        }
        
        return init;
    }

    /**
     * Duplique la grille.
     * @return Clone de la grille.
     */
    public Grille duplicate() {
    	
        Map<Position, Case> casesClone = new HashMap<Position, Case>();

        for (Case caseGrille : this.cases.values()) {
        	Case caseClone = new Case(caseGrille.getPosition(), caseGrille.getValeurs());
        	casesClone.put(caseClone.getPosition(), caseClone);
        }
        
        return new Grille(formatGrille, valeursRenseignees, casesClone);
    }

    /**
     * Redéfinit le hashcode d'une grille en fonction des cases dont elle est constituée.
     * @return Hashcode.
     */
    @Override
    public int hashCode() {
        int hash = 7;
        hash = 97 * hash + this.cases.hashCode();
        return hash;
    }

    /**
     * Redéfinit la comparaison entre 2 grilles en fonction des cases dont elles sont constituées.
     * @param obj Grille à comparer.
     * @return -1, 0 ou 1 si inférieur, égal ou supèrieur.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Grille other = (Grille) obj;
        if (this.cases != other.cases && (this.cases == null || !this.cases.equals(other.cases))) {
            return false;
        }
        return true;
    }

    /**
     * Retourne le format de la grille.
     * @return Format de la grille.
     */
    public FormatGrille getFormatGrille() {
        return formatGrille;
    }
}
